package core

import (
	"blockchain/crypto"
	"blockchain/types"
	"bytes"
	"encoding/gob"
	"io"
)

type Header struct {
	Version   uint32
	DataHash  types.Hash
	PrevBlock types.Hash
	Height    uint32
	Timestamp uint64
}

type Block struct {
	Header
	Transactions []Transaction
	Validator    crypto.PublicKey
	Signature    *crypto.Signature

	//Cache the hash of the Header hash
	hash types.Hash
}

func NewBlock(h *Header, txs []Transaction) *Block {
	return &Block{
		Header:       *h,
		Transactions: txs,
	}
}

func (b *Block) Encode(w io.Writer, enc Encoder[*Block]) error {
	return enc.Encode(w, b)
}

func (b *Block) Decode(r io.Reader, dec Decoder[*Block]) error {
	return dec.Decode(r, b)
}

func (b *Block) Hash(hasher types.Hasher[*Block]) types.Hash {
	if b.hash.IsZero() {
		b.hash = hasher.Hash(b)
	}

	return b.hash
}

func (b *Block) Sign(privKey *crypto.PrivateKey) error {
	sig, err := privKey.Sign(b.HeaderBytes())
	if err != nil {
		panic(err)
	}

	b.Validator = privKey.PublicKey()
	b.Signature = sig
	return nil
}

func (b *Block) Verify() bool {
	if b.Signature == nil {
		return false
	}

	return b.Signature.Verify(b.Validator, b.HeaderBytes())
}

func (b *Block) HeaderBytes() []byte {
	buf := bytes.Buffer{}
	enc := gob.NewEncoder(&buf)
	err := enc.Encode(b.Header)
	if err != nil {
		panic(err)
	}

	return buf.Bytes()
}

func RandomBlock(h uint32) *Block {
	b := &Block{
		Header: Header{
			Version:   1,
			DataHash:  types.RandomHash(),
			PrevBlock: types.RandomHash(),
			Height:    h,
			Timestamp: 1,
		},
		Transactions: nil,
		Signature:    nil,
		hash:         [32]byte{},
	}
	key := crypto.GeneratePrivateKey()
	_ = b.Sign(&key)
	b.Validator = key.PublicKey()
	b.hash = b.Hash(BlockHasher{})

	return b
}
